/*
  Copyright (C) 2011, Kyungjoo Kim
  
  This file is part of LINAL (LINear ALgebra)
  
  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
  
  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  Also add information on how to contact you by electronic and paper mail.
  
  
  Kyungjoo Kim
  iamkyungjoo@gmail.com
*/
#ifndef LINAL_MATRIX_HXX
#define LINAL_MATRIX_HXX

namespace linal {
  typedef struct FLA_Obj_struct FLA_Obj_base;
  typedef struct FLA_Obj_view FLA_Obj;
  typedef class  Matrix_*     Matrix;

  inline std::ostream& fla_view(std::ostream &os, const FLA_Obj &A) {
    os << "(" << A.offm << " , " << A.offn << ")" << "/"
       << "(" << A.m    << " , " << A.n    << ")" << "  " ;
    return os;
  }

  /*! 
    Matrix_ class is wrapping class for the FLA_Obj. 
    The class has some extensional properties to do safe free and malloc.
  */
  class Matrix_ : public Disp_ {
  private:
  protected:
    Int_  _created; //!< Flag indicate matrix is _created
    FLA_Obj _fla;  //!< FLA_Obj

    void set_view(Int_ m, Int_ n, Int_ offm, Int_ offn) {
      _fla.m    = m;    _fla.n    = n; 
      _fla.offm = offm; _fla.offn = offn;
    }
    void set_created(Int_ created) { _created = created; }
    void set_fla(FLA_Obj fla) { _fla = fla; }
    void set_fla_base_null() { _fla.base = NULL; }
    void set_fla_base(FLA_Obj_base* base) { _fla.base = base; }

  public:
    Matrix_() {
      set_created(0);
      set_view(0,0,0,0);
      set_fla_base_null();
    }
    Matrix_(FLA_Obj &fla) : _fla(fla), _created(0) { }
    Matrix_(const Matrix_&b) : _fla(b._fla), _created(0) { }

    virtual ~Matrix_() { free(); }
    // ---------------------------------------------------------
    // ** Create / Free
    void create(Int_ type, Int_ elem, Int_ m, Int_ n) {
      if (!m || !n) return;

      LINAL_ERROR( !_created,                   LINAL_ERROR_OBJ_CREATED );
      LINAL_ERROR( check_all_scalar_type(type), LINAL_ERROR_CHECK_SCALAR );
      LINAL_ERROR( check_all_elemtype(elem),    LINAL_ERROR_CHECK_ELEMTYPE );

      FLA_Obj_create_ext(type, elem, m, n, 0, 0, 0, 0, &_fla);
      set_created(1);
    }
    void free() {  
      if (_created) {
        FLA_Obj_free(&_fla);
        set_created(0);
      }
    }
    void create_buffer() { 
      if (get_m() && get_n())
        FLA_Obj_create_buffer( 0, 0, &_fla ); 
    }
    void free_buffer() {   
      if (_created)
        FLA_Obj_free_buffer( &_fla ); 
    }

    void set_zero() {  
      if (_created)
        FLA_Set( FLA_ZERO, _fla ); 
    }

    void wrap(FLA_Obj &fla)  {
      LINAL_ERROR( !_created, LINAL_ERROR_OBJ_CREATED );
      set_fla(fla);
    }

    // ---------------------------------------------------------
    // ** Query
    Bool_ is_created() const { return _created; }
    Bool_ is_base_null() const { return (_fla.base == NULL); }
    Bool_ is_buffer_null() const { 
      if (is_base_null())  return true;
      return (FLA_Obj_buffer_is_null(_fla)); 
    } 
    Bool_ is_hier() const { return (FLA_Obj_elemtype(_fla) == FLA_MATRIX); }
    Bool_ is_real_datatype() const {
      Int_ datatype = FLA_Obj_datatype(_fla);
      return (datatype == FLA_FLOAT || datatype == FLA_DOUBLE);
    }
    Bool_ is_complex_datatype() const {
      Int_ datatype = FLA_Obj_datatype(_fla);
      return (datatype == FLA_COMPLEX || datatype == FLA_DOUBLE_COMPLEX);
    }

    // ---------------------------------------------------------
    // ** Get interface
    // stride info
    Int_ get_cs() const    { return _fla.base->cs; }
    Int_ get_rs() const  { return _fla.base->rs; }
    // dimension info
    Int_ get_m() const    { return _fla.m; }
    Int_ get_n() const    { return _fla.n; }
    Int_ get_offm() const  { return _fla.offm; }
    Int_ get_offn() const { return _fla.offn; }
    // type and size info
    Int_ get_elemtype() const  { return FLA_Obj_elemtype(_fla); }
    Int_ get_elemtype_size() const { return FLA_Obj_elem_size(_fla); }
    Int_ get_datatype() const  { return FLA_Obj_datatype(_fla); }
    Int_ get_datatype_size() const { return FLA_Obj_datatype_size(get_datatype()); }
    Int_ get_buffer_size() const { return (get_datatype_size() * get_m() * get_n()); }
    void* get_buffer() const { 
      return FLA_Obj_buffer_at_view(_fla); 
    }
    FLA_Obj& get_fla() { return _fla; }

    void extract(Matrix_ &A, Int_ m, Int_ n, Int_ offm, Int_ offn) {
      LINAL_ERROR_DETAIL( ( offm+m <= get_m() && offn+n <= get_n() ),
                          LINAL_ERROR_OUT_OF_RANGE_FORMAT,
                          LINAL_ERROR_OUT_OF_RANGE, 
                          "range (", get_m(), ",", get_n(), ")", 
                          "offset(", offm,    ",", offn,          ")", 
                          "dim   (", m,       ",", n,             ")" );
      A.set_view(m, n, get_offm()+offm, get_offn()+offn);
      A.set_fla_base(_fla.base);
      return;
    }
    void extract(Matrix_ &A, Int_ m, Int_ n) { extract(A, m, n, 0, 0); }

    // ---------------------------------------------------------
    // ** Operators
    // return fla_obj
    FLA_Obj& operator ~()      { return get_fla(); }

    // copy the FLA_Obj in input matrix b. This share the buffer with b.
    Matrix_& operator=(const Matrix_ &b) { 
      _fla = b._fla;
      _created = 0;
      return *this;
    }

    // it compare the type and dimension.
    Bool_ operator==(Matrix_ &b) {
      return (get_m()        == b.get_m() &&
              get_n()        == b.get_n() &&
              get_datatype() == b.get_datatype() && 
              get_elemtype() == b.get_elemtype() );
    }
    Bool_ operator!=(Matrix_ &b) { return !(*this == b); }

    // return pointer to the position
    void* operator() (Int_ row, Int_ col) {
      LINAL_ERROR_DETAIL( ( row < get_m() && col < get_n() ),
                          LINAL_ERROR_OUT_OF_RANGE_FORMAT,
                          LINAL_ERROR_OUT_OF_RANGE,
                          "range (", get_m(), ",", get_n(), ")",
                          "offset(", 0,             ",", 0,             ")",
                          "index (", row,           ",", col,           ")" );
      return (void*)((Char_*)get_buffer() + 
                     get_elemtype_size() * (col*get_cs()+row));
    }

    virtual std::ostream& content(std::ostream& os) const {
      //this->Disp_::content(os);

      Int_ 
        m = get_m(), n = get_n(),
        offm = get_offm(), offn = get_offn();

      os << "offset (" << offm << "," << offn << ")" << " / " 
         << "dim    (" << m    << "," << n    << ")" << std::endl;

      if (is_base_null())
        os << "Base object is null" << std::endl;

      return os;      
    }
  };

  // ----------------------------------------------------------------------
  // ** Definition of interface functions
  /*!
    Access to the element in the matrix. 
    \param T [class] Template parameter that relavent to datatype created.
    \param buffer [in] Pointer to buffer
    \param row [in] row index
    \param col [in] column index
    \param cs [in] column stride
  */
  template<class T_>  T_& elem(void *buffer, Int_ cs,  Int_ rs, Int_ row,  Int_ col) {
    return ((T_*)buffer)[col*cs + row*rs]; 
  }
  template<class T_>  T_& elem(Matrix_ &A, Int_ row, Int_ col) {
    return elem<T_>(A.get_buffer(), A.get_cs(), A.get_rs(), row, col);
  }

#define LINAL_ELEM_EXT(type, name, row, col)                            \
  linal::elem< type >(buf ## name , cs ## name , rs ## name , row, col )

#define LINAL_ELEM(type, name, row, col)                        \
  linal::elem< type >(buf ## name , cs ## name , 1, row, col )


}
#endif
